class: title-slide, center, middle #.title[Introduzione a R] #.subtitle[Giornata 3 - Programmazione in R] <img src="data:image/png;base64,#img/arca_logo.svg" width="10%" style="display: block; margin: auto;" /> ###.location[Corsi ARCA - @DPSS] ###.author[Filippo Gambarota] --- # Programmazione in R Quello che vedremo in questa sezione sono i principali **costrutti della programmazione** e la loro applicazione in R. Ci sono alcuni punti da considerare: - Sono concetti trasversali estremamente utili - Sono alla base di qualcunque **funzionalità già implementata in R** - Vi permettono di fare qualunque cosa con il linguaggio --- # Programmazione in R - Disclaimer Ci sono delle cose che per tempo e complessità non possiamo affrontare e che sono R specifiche. Per questi aspetti avanzati del linguaggio, il libro [**Advanced R**](https://adv-r.hadley.nz/) è la cosa migliore <img src="data:image/png;base64,#img/adv_R.png" width="20%" style="display: block; margin: auto;" /> --- class: section, center, middle # Costrutti della programmazione in R --- # Costrutti della programmazione in R - Funzioni - Programmazione condizionale - Programmazione iterativa --- # Funzioni Analogalmente alle *funzioni matematiche* la funzione in programmazione consiste nell' **astrarre** una serie di operazioni (nel nostro caso una porzione di codice) definendo una serie di operazioni che forniti degli *input* forniscono degli *output* eseguendo una serie di *operazioni* --- # Funzioni Prendiamo l'equazione di una retta: `\(y = 2x + 3\)` dove `\(3\)` .pull-left[ ```r x <- 1:10 y <- 2*x + 3 ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-4-1.png" width="2100" style="display: block; margin: auto;" /> ] --- # Funzioni Se vogliamo *astrarre* questa operazione in modo da renderla più generale e utile dobbiamo definire: - **argomenti funzione**: quelle che in matematica sono le *variabili* - **corpo funzione**: le **operazioni** che la funzione deve eseguire usando gli argomenti - **output funzione**: cosa la funzione deve **restituire** come risultato --- # Funzioni - Argomenti Gli **argomenti** sono quelle parti variabili della funzione che vengono definiti e poi sono necessari ad eseguire la funzione stessa. Se vogliamo *astrarre* la retta che abbiamo visto prima dobbiamo definire alcune parti come **variabili**: La retta `\(y = mx + q\)` dove `\(m\)` è la pendenza, `\(x\)` sono i valori della variabile `\(x\)` e `\(q\)` è l'intercetta (valore di `\(y\)` quando `\(x = 0\)`). Quindi possiamo definire in R: ```r retta <- function(m, x, q){ # argomenti # body # output } ``` --- # Funzioni - Body Il corpo della funzione sono le operazioni da eseguire utilizzando gli argomenti in input. Nel caso della retta semplicemente moltiplicare `\(m\)` per ogni valore di `\(x\)` e aggiungere `\(q\)`. In questo modo otteniamo tutti i valori di `\(y\)`: ```r retta <- function(m, x, q){ # argomenti y <- m*x + q # output } ``` --- # Funzioni - Output L'output è il **risultato che la funzione ci restituisce** dopo aver eseguito tutte le operazioni. Nel nostro caso della retta, vogliamo ottenere il rispettivo valore di `\(y\)` per ogni valore di `\(x\)` inserito: ```r retta <- function(m, x, q){ # argomenti y <- m*x + q return(y) # restituisce y } ``` --- # Funzioni - Risultato finale .pull-left[ ```r retta <- function(m, x, q){ # argomenti y <- m*x + q return(y) # restituisce y } x <- 1:10 m <- 0.3 q <- 0 y <- retta(m, x, q) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-9-1.png" width="2100" style="display: block; margin: auto;" /> ] --- class: section, center, middle # Programmazione condizionale --- # Programmazione condizionale In programmazione solitamente è necessario non solo eseguire una serie di operazione **MA** eseguire delle operazione in funzione di alcune **condizioni** Facciamo un esempio pratico, la funzione `summary()` in R fornisce un risultato diverso in base al tipo di input. Come è possibile tutto questo? Tramite l'utilizzo di **condizioni**: ```r x <- 1:10 # vettore numerico y <- factor(rep(c("a", "b", "c"), each = 10)) # vettore di stringhe summary(x) ``` ``` ## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 1.00 3.25 5.50 5.50 7.75 10.00 ``` ```r summary(y) ``` ``` ## a b c ## 10 10 10 ``` --- # Programmazione condizionale Anche se non sappiamo quali operazioni svolga la funzione `summary()` possiamo immaginare una cosa simile ```r summary <- function(argomento){ # se l'argomento è un vettore numerico # esegui --> operazioni a,b,c # se l'argomento è un vettore stringa # esegui --> operazioni d,e,f # ... } ``` --- # Programmazione condizionale Il concetto di `se <condizione> allora fai <operazione>` si traduce in programmazione tramite quelli che si chiamano `if statement`: <img src="data:image/png;base64,#img/if_chart.png" width="40%" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per lavorare con gli `if statements` dobbiamo avere chiaro: - il concetto di *operatori logici* ovvero `TRUE` e `FALSE` - il concetto di *operazioni logiche* `TRUE and TRUE = TRUE` --- # Programmazione condizionale Quando una sola condizione non basta... <img src="data:image/png;base64,#img/ifelse_chart.png" width="40%" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per poter capire quale struttura condizionale utilizzare è importante capire bene il problema che dobbiamo risolvere. Ritornando all'esempio della funzione `summary()`, immaginiamo di avere 2 tipi di dati in R; stringhe e numeri. In questo caso è sufficiente avere un `if statement` che controlla se l'elemento è una stringa/numero e per tutto il resto applicare l'opposto. --- # Programmazione condizionale - Tip Esiste una famiglia di funzioni con prefisso `is.*` che fornisce `TRUE` quando la tipologia di oggetto corrisponde a quella richiesta e `FALSE` in caso contrario. ```r x <- 1:10 is.numeric(x) ``` ``` ## [1] TRUE ``` ```r is.factor(x) ``` ``` ## [1] FALSE ``` ```r is.character(x) ``` ``` ## [1] FALSE ``` Possiamo usare queste funzioni per creare un flusso condizionale nella nostra funzione `summary()` --- # Programmazione condizionale Scriviamo una funzione che restituisca la `media` quando il vettore è numerico e la tabella di frequenza (con la funzione `table()`) ```r my_summary <- function(x){ # testiamo la condizione if(is.numeric(x)){ return(mean(x)) }else{ return(table(x)) } } x <- 1:10 my_summary(x) ``` ``` ## [1] 5.5 ``` ```r x <- rep(c("a","b","c"), c(10, 2, 8)) my_summary(x) ``` ``` ## x ## a b c ## 10 2 8 ``` --- class: section, center, middle # Programmazione iterativa --- # Programmazione iterativa Il concetto di *iterazione* è alla base di qualsiasi operazione nei linguaggi di programmazione. In R molte delle operazioni sono vettorizzate. Questo rende il linguaggio più efficiente e pulito MA nasconde il concetto di *iterazione* --- # Programmazione iterativa Esempio: se io vi chiedo di usare la funzione `print()` per scrivere `"hello world"` nella console 10 volte, come fate? ```r msg <- "Hello World" print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` # Programmazione iterativa Quello che ci manca è un modo di ripetere una certa operazione, senza effettivamente ripetere il codice manualmente. Ci sono vari costrutti che ci permettono di ripetere operazioni: - Cicli `for` - Cicli `while` - `*apply` family - altri --- # For <img src="data:image/png;base64,#img/for_loop.png" width="40%" style="display: block; margin: auto;" /> --- # For La scrittura di un ciclo `for` è: ```r for(i in 1:n){ # operazioni } ``` --- # Scomponiamo il ciclo for Ci sono diversi elementi: - `for(){}`: è l'implementazione in R (in modo simile all'`if statement`) - `i`: questo viene chiamato *iteratore* o *indice*. E' un indice generico che può assumere qualsiasi valore e nome. Per convenzione viene chiamato `i`, `j` etc. Questo tiene conto del numero di iterazioni che il nostro ciclo deve fare - `in <valori>`: questo indica i valori che assumerà l'*iteratore* all'interno del ciclo - `{ # operazioni }`: sono le operazioni che i ciclo deve eseguire --- # Hello world come ciclo for ```r # vogliamo che il ciclo ripeta l'operazione 10 volte for(i in 1:10){ print("hello world") } ``` ``` ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ``` --- # Ma l'iteratore? La potenza del ciclo `for` sta nel fatto che l'iteratore `i` assume i valori del vettore specificato dopo `in`, uno alla volta: ```r for(i in 1:10){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ``` --- # For con iteratore vs senza Questa è una distinzione importante quanto sottile, notate la differenza tra questi due cicli: .pull-left[ ```r vec <- 1:5 for(i in 1:length(vec)){ print(vec[i]) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # While Il ciclo `while` è una versione più generale del ciclo for. Per funzionare utilizza una *condizione logica* e non un iteratore e un range di valori come nel `for`. ```r while(condizione){ # operazioni } ``` Dove il ciclo continuearà fino a che la `condizione` è vera --- # While - (Fun) Provate a scrivere questo ciclo `while` e vedere cosa succede: ```r x <- 10 while (x < 15) { print(x) } ``` Chi mi sa spiegare il risultato? --- # While Questo esercizio è utile per capire che il `while` è un ciclo non pre-determinato e quindi necessita sempre di un modo per essere interrotto, facendo diventare la condizione falsa. ```r x <- 5 while (x < 15) { print(x) x <- x + 1 } ``` ``` ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ## [1] 11 ## [1] 12 ## [1] 13 ## [1] 14 ``` --- # Applicazioni dei cicli Gli esempi finora sono semplici ma poco utili. Quando il queste strutture iterative sono veramente utili? Molte delle funzioni che utilizziamo come ad esempio `sum()`, `mean()`, etc. hanno al loro interno una sturttura iterativa Immaginiamo di non avere la funzione `sum()` e di volerla ricreare, come facciamo? Idee? --- # Somma come iterazione Scomponiamo concettualmente la somma, sommiamo i numeri da 1 a 10: - prendo il primo e lo sommo al secondo (`somma = 1 + 2`) - prendo la `somma` e la sommo al 3 elemento `somma = somma + 3` - ... In pratica abbiamo: - il nostro vettore da sommare - un oggetto `somma` che accumula progressivamente le somme precedenti --- # Somma come iterazione ```r somma <- 0 # inizializziamo la somma a 0 x <- 1:10 for(i in seq_along(x)){ somma <- somma + x[i] } ``` --- # Somma come iterazione Mettiamo tutto dentro una funzione ```r my_sum <- function(x){ somma <- 0 # inizializziamo la somma a 0 for(i in seq_along(x)){ somma <- somma + x[i] } return(somma) } x <- rnorm(100) my_sum(x) ``` ``` ## [1] 7.001607 ``` ```r sum(x) ``` ``` ## [1] 7.001607 ``` --- class: section, center, middle # Ma in R c'è qualcosa di meglio... --- # Ma in R c'è qualcosa di meglio... In R, l'utilizzo **esplicito** dei cicli `for` non è molto diffuso, per 2 motivi: - R è un linguaggio fortemente **funzionale** - R è un linguaggio spesso **vettorizzato** - I cicli `for` sono molto verbosi e non sempre leggibili - I cicli `for` in R, se non scritti bene, possono essere *estremamente lenti* --- class: section, center, middle # `*apply` family --- # `*apply` family Immaginate di avere una `lista` di vettori, e di voler applicare la stessa funzione/i ad ogni elemento della lista. Come fare? ^[1] - applico manualmente la funzione selezionando gli elementi - ciclo `for` che itera sugli elementi della lista e applica la funzione/i - ... ```r my_list <- list( vec1 <- rnorm(100), vec2 <- runif(100), vec3 <- rnorm(100), vec4 <- rnorm(100) ) ``` .footnote[ Hadley Wickam - The joy of functional programming - [link](https://www.youtube.com/watch?v=bzUmK0Y07ck&t=1453s) ] --- # `*apply` family Applichiamo `media`, `mediana` e `deviazione standard`: .pull-left[ ```r means <- vector(mode = "numeric", length = length(my_list)) medians <- vector(mode = "numeric", length = length(my_list)) stds <- vector(mode = "numeric", length = length(my_list)) for(i in 1:length(my_list)){ means[i] <- mean(my_list[[i]]) medians[i] <- median(my_list[[i]]) stds[i] <- sd(my_list[[i]]) } ``` ] .pull-right[ ```r means ``` ``` ## [1] -0.08751766 0.51361345 0.12975840 0.21200889 ``` ```r medians ``` ``` ## [1] -3.255022e-02 5.027104e-01 9.626745e-05 2.632156e-01 ``` ```r stds ``` ``` ## [1] 0.8024532 0.2853269 1.1037257 0.9951613 ``` ] --- # `*apply` family Funziona tutto! ma: - il `for` è molto laborioso da scrivere gli indici sia per la lista che per il vettore che stiamo popolando - dobbiamo *pre-allocare delle variabili* (per il motivo della velocità che dicevo) - 8 righe di codice (per questo esempio semplice) --- # `*apply` family In R è presente una famiglia di funzioni `*apply` come `lapply`, `sapply`, etc. che permettono di ottenere lo stesso risultato in modo più conciso, rapido e semplice: ```r means <- sapply(my_list, mean) medians <- sapply(my_list, median) stds <- sapply(my_list, sd) means ``` ``` ## [1] -0.08751766 0.51361345 0.12975840 0.21200889 ``` ```r medians ``` ``` ## [1] -3.255022e-02 5.027104e-01 9.626745e-05 2.632156e-01 ``` ```r stds ``` ``` ## [1] 0.8024532 0.2853269 1.1037257 0.9951613 ``` --- # `*apply` family - Bonus Prima di introdurre l'`*apply` family un piccolo bonus. Sfruttando il fatto che in R **tutto è un oggetto** possiamo scrivere in modo ancora più conciso: ```r my_funs <- list(median = median, mean = mean, sd = sd) lapply(my_list, function(vec) sapply(my_funs, function(fun) fun(vec))) ``` ``` ## [[1]] ## median mean sd ## -0.03255022 -0.08751766 0.80245319 ## ## [[2]] ## median mean sd ## 0.5027104 0.5136134 0.2853269 ## ## [[3]] ## median mean sd ## 9.626745e-05 1.297584e-01 1.103726e+00 ## ## [[4]] ## median mean sd ## 0.2632156 0.2120089 0.9951613 ``` Amazing! ora cerchiamo di dare un senso a queste righe di codice! --- # `*apply` family ```r *apply(<lista>, <funzione>) ``` - cosa può essere la `lista`? - lista - dataframe - vettore - cosa può essere la `funzione`? - funzione *base* o importata *pacchetto* - funzione *custom* - funzione *anonima* --- # `*apply` family - intuizione Prima di analizzare l'`*apply` family, credo sia utile un ulteriore parallelismo con il ciclo `for` che abbiamo visto. `*apply` non è altro che un ciclo `for`, leggermente semplificato: .pull-left[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` family - spoiler funzione anonima Quindi come il ciclo `for` scritto come `i in vec` assegna al valore `i` un elemento per volta dell'oggetto `vec`, internamente le funzioni `*apply` prendono il primo elemento dell'oggetto in input (`lista`) e applicano direttamente la funzione che abbiamo scelto. C'è un modo per rendere esplicito questo, anche nelle funzioni `*apply`: .pull-left[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, function(i) print(i)) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` e funzioni custom ```r center_var <- function(x){ x - mean(x) } my_list <- list( vec1 = runif(10), vec2 = runif(10), vec3 = runif(10) ) lapply(my_list, center_var) ``` ``` ## $vec1 ## [1] -0.26876782 -0.16383876 -0.42514329 0.15782423 -0.24715772 -0.28469161 0.29419622 ## [8] 0.45894119 0.44942843 0.02920914 ## ## $vec2 ## [1] -0.36679048 -0.03077218 -0.01638125 0.22822659 0.30319081 -0.18430069 -0.14154366 ## [8] 0.19951475 -0.13965158 0.14850769 ## ## $vec3 ## [1] 0.1463017 -0.3057345 -0.3851192 0.3504302 -0.2160624 -0.2902385 0.2352256 ## [8] 0.0453294 0.5283361 -0.1084685 ``` --- # `*apply` e funzioni anonime Una funzione anonima è una funzione non salvata in un oggetto ma scritta per essere **eseguita direttamente**, all'interno di altre funzioni che lo permettono: ```r lapply(my_list, function(x) x - mean(x)) ``` ``` ## $vec1 ## [1] -0.26876782 -0.16383876 -0.42514329 0.15782423 -0.24715772 -0.28469161 0.29419622 ## [8] 0.45894119 0.44942843 0.02920914 ## ## $vec2 ## [1] -0.36679048 -0.03077218 -0.01638125 0.22822659 0.30319081 -0.18430069 -0.14154366 ## [8] 0.19951475 -0.13965158 0.14850769 ## ## $vec3 ## [1] 0.1463017 -0.3057345 -0.3851192 0.3504302 -0.2160624 -0.2902385 0.2352256 ## [8] 0.0453294 0.5283361 -0.1084685 ``` Come per i cicli `for` (ricordo che `*apply` e `for` sono identici), `x` è solo un placeholder (analogo di `i`) e può essere qualsiasi lettera o nome --- # Tutte le tipologie di `*apply` Vediamo tutti i tipi di `*apply` che ci sono. Alcuni sono più *utili* altri più *robusti* e altri ancora poco utilizzati: - `lapply()`: la funzione di base - `sapply()`: `simplified-apply` - `tapply()`: poco utilizzata, utile con i *fattori* - `apply()`: utile per i *dataframe/matrici* - `mapply()`: versione multivariata, utilizza *più liste contemporaneamente* - `vapply()`: utilizzata dentro le funzioni e pacchetti --- # `lapply` `lapply` sta per list-apply e restituisce sempre una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- lapply(my_list, mean) res ``` ``` ## $vec1 ## [1] 0.4646226 ## ## $vec2 ## [1] 0.4390514 ## ## $vec3 ## [1] 0.4080462 ``` ```r class(res) ``` ``` ## [1] "list" ``` --- # `sapply` `sapply` sta per simplified-apply e (cerca) di restituire una versione più semplice di una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- sapply(my_list, mean) res ``` ``` ## vec1 vec2 vec3 ## 0.4646226 0.4390514 0.4080462 ``` ```r class(res) ``` ``` ## [1] "numeric" ``` --- # `apply` `apply` funziona in modo specifico per dataframe o matrici, applicando una funzione alle righe o alle colonne: - `apply(dataframe, index, fun)` ```r # index 1 = riga, 2 = colonna my_dataframe <- data.frame(my_list) head(my_dataframe) ``` ``` ## vec1 vec2 vec3 ## 1 0.1958548 0.07226096 0.55434783 ## 2 0.3007838 0.40827926 0.10231169 ## 3 0.0394793 0.42267019 0.02292701 ## 4 0.6224468 0.66727803 0.75847637 ## 5 0.2174649 0.74224225 0.19198379 ## 6 0.1799310 0.25475075 0.11780770 ``` ```r apply(my_dataframe, 1, mean) ``` ``` ## [1] 0.2741545 0.2704583 0.1616922 0.6827337 0.3838970 0.1841631 0.5665328 0.6718352 ## [9] 0.7166110 0.4603228 ``` ```r apply(my_dataframe, 2, mean) ``` ``` ## vec1 vec2 vec3 ## 0.4646226 0.4390514 0.4080462 ``` ```r apply(my_dataframe, 2, center_var) ``` ``` ## vec1 vec2 vec3 ## [1,] -0.26876782 -0.36679048 0.1463017 ## [2,] -0.16383876 -0.03077218 -0.3057345 ## [3,] -0.42514329 -0.01638125 -0.3851192 ## [4,] 0.15782423 0.22822659 0.3504302 ## [5,] -0.24715772 0.30319081 -0.2160624 ## [6,] -0.28469161 -0.18430069 -0.2902385 ## [7,] 0.29419622 -0.14154366 0.2352256 ## [8,] 0.45894119 0.19951475 0.0453294 ## [9,] 0.44942843 -0.13965158 0.5283361 ## [10,] 0.02920914 0.14850769 -0.1084685 ``` --- # `tapply` `tapply` permette di applicare una funzione ad un *vettore*, dividendo questo vettore in base ad una variabile categoriale: - `tapply(dataframe, index, fun)`: dove `index` è un vettore di stringa o un fattore ```r vec <- rnorm(75) index <- rep(c("a", "b", "c"), each = 25) tapply(vec, index, mean) ``` ``` ## a b c ## 0.05710244 0.19735113 -0.28657632 ``` --- # `vapply` `vapply` è una versione più *solida* delle precedenti dal punto di vista di programmazione. In pratica permette (e richiede) di specificare in anticipo la tipologia di dato che ci aspettiamo come risultato `vapply(X = , FUN = , FUN.VALUE = ,... )` ```r vapply(my_list, FUN = mean, FUN.VALUE = numeric(length = 1)) ``` ``` ## vec1 vec2 vec3 ## 0.4646226 0.4390514 0.4080462 ``` - `my_list, FUN = mean`: è esattamente uguale a `sapply/lapply` - `FUN.VALUE = numeric(length = 1)`: indica che ogni risultato è un singolo valore numerico --- # `mapply` Questa è quella più complicata ma anche molto utile. Praticamente permette di gestire più liste contemporaneamente per scenari più complessi. Ad esempio vogliamo usare la funzione `rnorm()` e generare vettori con diverse **medie** e **deviazioni stardard** in combinazione. ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` ``` ## [[1]] ## [1] 10.786422 10.863932 9.651546 9.012362 9.501867 10.759893 9.949586 10.823735 ## [9] 10.956219 11.098710 ## ## [[2]] ## [1] 21.09090 20.10529 18.81062 22.66016 18.99384 20.31918 20.50426 21.44367 17.60905 ## [10] 17.11278 ## ## [[3]] ## [1] 27.81145 25.93063 28.08978 30.50761 23.94013 29.84909 35.98019 31.10264 26.20058 ## [10] 32.90096 ## ## [[4]] ## [1] 37.49874 38.82879 39.61500 39.79749 34.05443 41.93324 39.78216 47.24417 42.37247 ## [10] 43.61903 ``` **IMPORTANTE**, tutte le liste incluse devono avere la stessa dimensione! --- # `mapply` ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` - `function(...)`: è una funzione anonima come abbiamo visto prima che può avere *n* elementi - `rnorm(n = 10, mean = x, sd = y)`: è l'effettiva funzione anonima dove abbiamo i placeholders `x` and `y` - `medie, stds`: sono **in ordine** le liste corrispondenti ai placeholders indicati, quindi `x = medie` e `y = stds`. - `SIMPLIFY = FALSE`: semplicemente dice di restituire una lista e non cercare (come `sapply`) di semplificare il risultato --- # `mapply` come `for` Lo stesso risultato (in modo più verboso e credo meno intuitivo) si ottiene con un `for` usando più volte l'iteratore `i`: ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) res <- vector(mode = "list", length = length(medie)) for(i in 1:length(medie)){ res[[i]] <- rnorm(10, mean = medie[[i]], sd = stds[[i]]) } res ``` ``` ## [[1]] ## [1] 8.837498 8.784753 11.220302 9.876003 10.899714 10.342042 9.515918 11.062741 ## [9] 11.594288 9.792820 ## ## [[2]] ## [1] 21.56708 21.11608 18.26047 21.44960 18.98693 24.07965 17.72027 21.12303 16.95493 ## [10] 18.91417 ## ## [[3]] ## [1] 27.32531 30.00583 27.02168 26.84310 35.25669 27.99050 28.08093 28.41747 27.31437 ## [10] 29.23273 ## ## [[4]] ## [1] 39.96207 42.92679 39.03741 47.16645 43.80672 34.11469 33.78534 42.59347 36.13538 ## [10] 42.34749 ``` --- class: section, center, middle # `*apply` alcune precisazioni --- # `*apply` vettore vs lista Abbiamo sempre usato esplicitamente `liste` fino ad ora, ma le funzioni `*apply` sono direttamente applicabili anche a **vettori** - se usiamo un vettore di *n* elementi, allora itereremo da `1:n` - se usiamo una lista di *n* elementi, allora iteriamo da `1:n` dove il singolo elemento può essere qualsiasi cosa ```r my_vec <- 1:5 my_list <- list(a = 1:2, b = 3:4, c = 5:6) res <- sapply(my_vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ```r res <- sapply(my_list, print) ``` ``` ## [1] 1 2 ## [1] 3 4 ## [1] 5 6 ``` --- # `*apply` come un `for` Nulla ci vieta (ma perdiamo l'aspetto intuitivo e conciso) di usare le funzioni `*apply` esattamente come un ciclo `for`, usando un **iteratore**: ```r medie <- c(10, 20, 30, 40) stds <- c(1,2,3,4) res <- lapply(1:length(medie), function(i){ rnorm(n = 10, mean = medie[i], sd = stds[i]) }) ``` Trovo tuttavia più chiara l'alternativa usando `mapply`: ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` --- class: section, center, middle # Extra: `purrr::map*` --- # Extra: `purrr::map*` .pull-left[ <img src="data:image/png;base64,#img/purrr.svg" width="60%" style="display: block; margin: auto;" /> ] .pull-right[ Senza addentrarci troppo in questo modo, c'è una famiglia di funzioni che una volta imparato `*apply` vi consiglio di usare perchè più consistenti e intuitive, la `map*` family. ] --- # Extra: `purrr::map*` Per usare `purrr::map*` è sufficiente installare il pacchetto `purrr` con `install.packages("purrr")` ed iniziare ad usare le nuove funzioni. La sintassi è esattamente la stessa di `*apply` (qualche modifica ma potete usare la stessa) ma invece che usare una funzione per tutto, abbiamo molte funzioni per ogni casistica: - `map(lista, funzione)` è l'analogo di `lapply()` e fornisce sempre una lista - `map_dbl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore di *double* - `map_lgl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore *logico* - `map2/pmap_*` sono rispettivamente applicare la funzione a 2/n liste (analogo di `mapply()`) --- class: section, center, middle # Extra: `replicate()` and `repeat()` --- # Extra: `replicate()` and `repeat()` Ci sono altre due funzioni in R che permettono di *iterare*. Sono meno utilizzate perchè si ottengono gli stessi risultati usando un semplice `for` o `*apply`. - `replicate()` permette di ripetere un operazione *n* volte, senza però utilizzare un `iteratore` o un `placeholder`. - `repeat()` anche repeat permette di ripetere ma fino a che non si verifica un certa condizione (**logica**). Ha una struttura simile al ciclo `while` --- class: section, center, middle # Extra: Formula syntax --- # Formula syntax In R molte operazioni vengono eseguite usando la **formula syntax** `something ~ something else` ad esempio: - modelli statistici: `lm(y ~ x, data = data)`, `t.test(y ~ factor, data = data)` - plot: `boxplot(y ~ x, data = data)` - ... In cosa consiste? --- # Formula syntax Senza andare nei dettagli tecnici, R usa una cosa che si chiama *lazy evaluation*. In altri termini "salva" delle operazioni per essere eseguite in un secondo momento. Tutti sappiamo che se scriviamo un nome (senza virgolette) e questo non è associato ad un oggetto otteniamo un errore. Tuttavia alcune funzioni come `library()` non forniscono errore. Perchè? ```r stats # errore ``` ``` ## Error in eval(expr, envir, enclos): object 'stats' not found ``` ```r library(stats) # no errore ``` --- # Formula syntax La ragione è che R è in grado di salvare un'espressione per usarla poi in uno specifico contesto (ad esempio dentro una funzione). La `formula syntax` è un esempio. Usando la tilde `~` possiamo creare delle `formule` che R può utilizzare in specifici contesti: ```r y ``` ``` ## [1] a a a a a a a a a a b b b b b b b b b b c c c c c c c c c c ## Levels: a b c ``` ```r x ``` ``` ## [1] -0.151003476 -1.099348489 0.540432822 0.458591953 0.016685530 0.500629893 ## [7] 1.708775624 -0.999475883 -0.158915035 -0.915340317 0.250094939 0.486586581 ## [13] 0.144330099 -1.020584380 1.126488658 1.130921147 -0.273918259 0.083200210 ## [19] 0.499480658 -1.438866577 -0.884970983 -0.337773957 -0.233082717 1.193884397 ## [25] -0.648704491 1.112918080 -1.217889778 -1.558418893 0.006723405 -0.261239555 ## [31] -0.058569206 -1.232180127 -0.202036152 0.882369153 -1.342726454 -0.220857860 ## [37] -0.536569551 0.144894676 1.267987465 -0.505288991 -0.504716549 -1.166136535 ## [43] -0.051085177 1.423636840 0.100238204 0.634023907 1.303155888 1.794500751 ## [49] -1.272651632 0.906075287 -0.580253435 0.182233200 -0.399048000 0.092651126 ## [55] 0.243967637 0.633720242 -0.542814703 1.434467835 0.279094277 -0.431994492 ## [61] 0.462425206 0.118184201 0.435277236 0.959797034 0.637423959 -1.968693830 ## [67] -0.280642258 0.510552721 1.166061838 -0.130939687 1.601139181 -0.662017655 ## [73] -2.126859027 0.735221755 -0.475723649 0.429940352 1.209515915 0.907481725 ## [79] 0.436375866 0.135108050 1.737071473 1.411777412 -0.155708265 0.971128034 ## [85] -1.380406474 0.543584135 0.427181014 -1.903698251 1.023633023 0.907138857 ## [91] -0.393074892 1.943733747 -1.364921138 -1.664115496 -1.437179986 1.302504680 ## [97] 2.189712412 0.606649129 -0.417797380 -1.781532943 ``` ```r y ~ x ``` ``` ## y ~ x ## <environment: 0x0000013622566610> ``` ```r my_formula <- y ~ x class(my_formula) ``` ``` ## [1] "formula" ``` --- # Formula syntax e `aggregate()` Un esempio utile è la funzione `aggregate()` molto interessante per applicare funzioni a dataframe. Immaginate di avere il dataset `iris` e calcolare la media per ogni livello del fattore `Species`: ```r tapply(iris$Sepal.Length, iris$Species) ``` ``` ## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ## [43] 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 ## [85] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ## [127] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ``` ```r aggregate(Sepal.Length ~ Species, FUN = mean, data = iris) ``` ``` ## Species Sepal.Length ## 1 setosa 5.006 ## 2 versicolor 5.936 ## 3 virginica 6.588 ``` ```r # Anche creando un oggetto, ma solo come formula my_formula <- Sepal.Length ~ Species my_char <- "Sepal.Length ~ Species" aggregate(my_char, FUN = mean, data = iris) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): argument "by" is missing, with no default ``` ```r # Viene lo stesso usando $ e senza specificare data = aggregate(iris$Sepal.Length, iris$Species, FUN = mean) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): 'by' must be a list ``` --- # Formula syntax e `aggregate()` Ma anche operazioni più complesse: ```r my_iris <- iris my_iris$fac <- rep(c("a", "b", "c"), 50) aggregate(Sepal.Length ~ Species + fac, mean, data = my_iris) ``` ``` ## Species fac Sepal.Length ## 1 setosa a 5.052941 ## 2 versicolor a 5.770588 ## 3 virginica a 6.756250 ## 4 setosa b 5.011765 ## 5 versicolor b 6.018750 ## 6 virginica b 6.447059 ## 7 setosa c 4.950000 ## 8 versicolor c 6.023529 ## 9 virginica c 6.570588 ``` --- # Replicate .pull-left[ `replicate(n, expr)` - `n` è il numero di ripetizioni - `expr` è la porzione di codice da ripetere ```r # Campioniamo 1000 volte da una normale e facciamo la media AKA distribuzione campionaria della media nrep <- 1000 nsample <- 30 media <- 100 ds <- 30 means <- replicate(n = nrep, expr = { mean(rnorm(nsample, media, ds)) }) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-57-1.png" width="2100" style="display: block; margin: auto;" /> ] --- # `repeat()` ```r repeat { # cose da ripetere if(...){ # condizione da valutare break # ferma il loop } } ``` ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` --- # `repeat()` vs `while` <!-- TODO revise repeat vs loop --> .pull-left[ ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] .pull-right[ ```r i <- 1 while(i < 4){ print(i) i <- i + 1 } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] - `repeat` valuta la condizione una volta finita l'iterazione, mentre `while` all'inizio. Se la condizione non è `TRUE` all'inizio, il `while` non parte mentre `repeat` si.